package evs.location;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.search.Search;
import org.scannotation.AnnotationDB;
import org.scannotation.WarUrlFinder;

import evs.security.SecureServlet;

/**
 * Servlet implementation class MainServlet
 */
//@WebServlet( "/*" )
public class LocationServlet extends SecureServlet
{
    private static final long serialVersionUID = -84376582475843561L;

    private static Object _initializationLock = new Object();
    private static EntityMap _entityMap;
    private static SessionFactory _sessionFactory;

    private static ArrayList<Class<?>> _requestInterceptors = new ArrayList<Class<?>>();
    
    private static boolean _initialized = false;
    
    public LocationServlet()
    {
        super();
    }

    private void _scanAnnotations()
    {
        if ( _entityMap == null )
        {
            URL url = WarUrlFinder.findWebInfClassesPath( this.getServletContext() );
            URL [] urls = WarUrlFinder.findWebInfLibClasspaths( this.getServletContext() );
            
            log( "CLASSES URL = " + url.toString() );
            
            for ( URL u : urls )
                log( "LIB URL = " + u.toString() );
            
            AnnotationDB adb = new AnnotationDB();
            
            adb.setScanClassAnnotations( true );
            adb.setScanFieldAnnotations( false );
            adb.setScanMethodAnnotations( false );
            adb.setScanParameterAnnotations( false );
            
            try
            {
                adb.scanArchives( url );
                adb.scanArchives( urls );
                
                Set<Class<?>> classes = new HashSet<Class<?>>();
                
                for ( Entry<String, Set<String>> entry : adb.getClassIndex().entrySet() )
                {
                    if ( entry.getValue().contains( javax.persistence.Entity.class.getName() ) )
                    {
                        Class<?> resourceClass = Class.forName( entry.getKey() );
                        classes.add( resourceClass );
                        
                        log( "Found @Entity class: " + entry.getKey() );
                    }
                    
                    if ( entry.getValue().contains( Interceptor.class.getName() ) )
                    {
                        Class<?> interceptorClass = Class.forName( entry.getKey() );
                        
                        if ( RequestInterceptor.class.isAssignableFrom( interceptorClass ) )
                        {
                            log( "Found @Interceptor class: " + interceptorClass.getName() );
                            _requestInterceptors.add( interceptorClass );
                        }
                        else
                        {
                            log( "Found INVALID @Interceptor class: " + interceptorClass.getName() );
                            log( interceptorClass.getName() + " is annotated with @Interceptor but " +
                            		"does not implement a valid interceptor interface!" );
                        }
                    }
                }
                
                _entityMap = new EntityMap( classes );
                
                Collections.sort( _requestInterceptors, new Comparator<Class<?>>()
                    {

                        @Override
                        public int compare( Class<?> o1, Class<?> o2 )
                        {
                            Integer value1 = o1.getAnnotation( Interceptor.class ).priority();
                            Integer value2 = o2.getAnnotation( Interceptor.class ).priority();
                            
                            return value1.compareTo( value2 );
                        }
                    }
                );
            }
            catch ( IOException e )
            {
                log( "Error scanning for annotations", e );
            }
            catch ( ClassNotFoundException e )
            {
                log( "Error scanning for annotations", e );
            }   
        }
    }

    @Override
    public void init() throws ServletException
    {
        super.init();
        
        synchronized ( _initializationLock )
        {
        	if ( _initialized )
        		return;
        	
            _scanAnnotations();

            Configuration config = new Configuration().configure();
            
            for ( Class<?> entityClass : _entityMap.getEntityClasses() )
                config.addAnnotatedClass( entityClass );
            
            _sessionFactory = config.buildSessionFactory();
            
            Session s = _sessionFactory.openSession();
            
            try
            {
                log( "Trying to create search index..." );
                
                Search.getFullTextSession( s ).createIndexer( ).startAndWait();
                log( "Search index created successfully." );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace();
            }
            
            s.close();
            
            _initialized = true;
        }
    }


    protected void doGet( HttpServletRequest request, HttpServletResponse response )
                    throws ServletException, IOException
    {
        try
        {
            _checkAllowRequest( request );
            
            String requestPath = AbstractRequestHandler.extractPath( request ).toLowerCase();
            
            AbstractRequestHandler requestHandler;
            
            if ( requestPath.startsWith( "search" ) )
            {
                requestHandler = new SearchRequestHandler( request, _sessionFactory, _requestInterceptors, _entityMap );
            }
            else if ( requestPath.startsWith( "fulltext-search" ) )
            {
                requestHandler = new FulltextSearchRequestHandler( request, _sessionFactory, _requestInterceptors, _entityMap );
            }
            else
            {
                requestHandler = new GetRequestHandler( request, _sessionFactory, _requestInterceptors, _entityMap );
            }
            
            requestHandler.execute( response );
        }
        catch ( EvsException e )
        {
            response.sendError( e.HTTP_CODE, e.getMessage() );
        }
    }

    protected void doPost( HttpServletRequest request, HttpServletResponse response )
                    throws ServletException, IOException
    {
        try
        {
            _checkAllowRequest( request );
            
            AbstractRequestHandler requestHandler = 
                new PostRequestHandler( request, _sessionFactory, _requestInterceptors, _entityMap );
            
            requestHandler.execute( response );
        }
        catch ( EvsException e )
        {
            response.sendError( e.HTTP_CODE, e.getMessage() );
        }
    }
 
    
    protected void doPut( HttpServletRequest request, HttpServletResponse response )
                    throws ServletException, IOException
    {
        try
        {
            _checkAllowRequest( request );
            
            AbstractRequestHandler requestHandler = 
                new PutRequestHandler( request, _sessionFactory, _requestInterceptors, _entityMap );
            
            requestHandler.execute( response );
        }
        catch ( EvsException e )
        {
            response.sendError( e.HTTP_CODE, e.getMessage() );
        }
    }


    protected void doDelete( HttpServletRequest request, HttpServletResponse response )
                    throws ServletException, IOException
    {
        try
        {
            _checkAllowRequest( request );
            
            AbstractRequestHandler requestHandler = 
                new DeleteRequestHandler( request, _sessionFactory, _requestInterceptors, _entityMap );
            
            requestHandler.execute( response );
        }
        catch ( EvsException e )
        {
            response.sendError( e.HTTP_CODE, e.getMessage() );
        }
    }

    @Override
    protected boolean _allowOnlyLocalAccess() 
    {
        return true;
    }
}
